#include "SysFrustumCull.h"

//-------------------------------------------------------------------------------------------------------
SysFrustumCull::SysFrustumCull()
{ }

//-------------------------------------------------------------------------------------------------------
SysFrustumCull::~SysFrustumCull()
{ }

//-------------------------------------------------------------------------------------------------------
void SysFrustumCull::CullInstanceMaps(
	ID3D11DeviceContext*	dc,							// Pointer to the application device context.
	vector<InstanceMap*>&	instanceMaps,				// Reference to the application InstanceMap vector.
	vector<ModelData*>&		modelData,					// Reference to the application ModelData vector.
	Camera*					pCamera)					// Pointer to the camera.
{
	// Iterate through all instance maps in the level.
	for (UINT i = 0; i < instanceMaps.size(); ++i)
	{
		// Cull instances that are not visible to the camera.
		CullInstances(dc, instanceMaps[i], modelData[i], pCamera);
	}
}

//-------------------------------------------------------------------------------------------------------
bool SysFrustumCull::CullInstances(						// Method performs frustum culling on the passed in InstanceMap.
	ID3D11DeviceContext*	deviceContext,				// Pointer to the application device context.
	InstanceMap*			instanceMap, 				// Pointer to an instance map.
	ModelData*				modelData, 					// Pointer to a ModelData object to retrieve it's AABB.
	Camera*					pCamera)					// Pointer to the camera.
{
	int visibleInstances = 0;

	// Calculate the inverse of the camera's view matrix.
	XMVECTOR detView = XMMatrixDeterminant(pCamera->View());
	XMMATRIX invView = XMMatrixInverse(&detView, pCamera->View());

	// Map the InstanceMap dynamic vertex buffer do we can write the visible instance data to it.
	D3D11_MAPPED_SUBRESOURCE mappedData;				// Subresource variable to store mapped data pointer.

	deviceContext->Map(									// Map the InstanceMap dynamic vertex buffer.
		instanceMap->GetInstancedBuffer(), 0,			// Retrieve dynamic vertex buffer pointer from InstanceMap.
		D3D11_MAP_WRITE_DISCARD, 0,						// Specify "write discard" flag to write new data to buffer.
		&mappedData);

	// Create a pointer to access the mapped instanced data.
	InstancedData* dataView = reinterpret_cast<InstancedData*>(mappedData.pData);

	// Get a pointer to the InstanceMap instance data on CPU memory.
	vector<InstancedData>* pInstancedData = instanceMap->GetInstancedDataP();
	
	// Loop through each instanced object in the scene and determine if the camera can see it.
	for (UINT i = 0; i < instanceMap->GetInstancedDataP()->size(); ++i)
	{
		// Calculate the inverse world matrix of the curent object.
		XMFLOAT4X4 world	= (*pInstancedData)[i].World;
		XMMATRIX W			= XMLoadFloat4x4(&world);
		XMMATRIX invWorld	= XMMatrixInverse(&XMMatrixDeterminant(W), W);

		// Calculate the matrix to transform the camera frustum from view space to local space.
		XMMATRIX toLocal	= XMMatrixMultiply(invView, invWorld);

		// Decompose the matrix into its individual parts.
		XMVECTOR scale, rotQuat, translation;
		XMMatrixDecompose(&scale, &rotQuat, &translation, toLocal);

		// Transform the camera frustum from view space to the current object's local space.
		XNA::Frustum localSpaceFrustum;
		XNA::TransformFrustum(
			&localSpaceFrustum,			// [Out] The generated local space frustum.
			pCamera->GetFrustum(),		// [In]  The camera's frustum.
			XMVectorGetX(scale),		// [In]  Scale data to transform the input frustum.
			rotQuat,					// [In]  Rotation data to transform the input frustum.
			translation);				// [in]  Translation data to transfor the input frustm.

		// Perform the box/frustum intersection test in local space.
		if (XNA::IntersectAxisAlignedBoxFrustum(modelData->GetAABBp(), &localSpaceFrustum) != 0)
		{
			// Write the visible object's instance data to the InstanceMap dynamic vertex buffer.
			dataView[visibleInstances].World = (*pInstancedData)[i].World;
			dataView[visibleInstances].Color = (*pInstancedData)[i].Color;

			// Increment the instance visible count.
			++visibleInstances;
		}
	}

	// Update visible instances for the corresponding InstanceMap.
	instanceMap->SetNumVisible(visibleInstances);

	// Unmap the InstanceMap dynamic vertex buffer.
	deviceContext->Unmap(instanceMap->GetInstancedBuffer(), 0);

	return true;
}

//-------------------------------------------------------------------------------------------------------
void SysFrustumCull::DrawInstances(					// Method draws all visible instances in an InstanceMap.
	ID3D11DeviceContext*	deviceContext,			// Pointer to the application device context.
	InstanceMap*			instanceMap,			// Pointer to an instance map.
	ModelData*				modelData, 				// Pointer to a ModelData object to retrieve VB and IB.
	Camera*					pCamera, 				// Pointer to the camera.
	Material*				pMaterial)				// Pointer to material for rendering.
{
	// Get a pointer to the InstancedShader instance.
	InstancedShader* shader = InstancedShader::GetInstance();

	// Setup the pipeline by specifying the input layout and primitive topology.
	deviceContext->IASetInputLayout(shader->GetInputLayout());
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Get the camera's current view-projection matrix.
	XMMATRIX VP = pCamera->ViewProj();

	// Specify the byte-stride corresponding to the two vertex buffers we are binding to the pipeline.
	UINT stride[2] = { sizeof(InstancedShader::Vertex), sizeof(InstancedData) };
	UINT offset[2] = { 0, 0 };

	// Specify the pointers of the vertex buffers we are binding to the pipeline.
	ID3D11Buffer* vbs[2] = { 
		modelData	->GetVertexBuffer(),				// Buffer storing the vertices to render.
		instanceMap	->GetInstancedBuffer() };			// Buffer storing each instances world matrix and colour.
	
	ID3DX11EffectTechnique* pTechnique = shader->Light3Tech;
	D3DX11_TECHNIQUE_DESC techDesc;
	pTechnique->GetDesc(&techDesc);

	// Get index count for model.
	int indexCount = modelData->GetIndexCount();

	// Draw the model's vertex/index buffer based on instance map.
	for (UINT p = 0; p < techDesc.Passes; ++p)
	{
		// Set the vertex and index buffers to render.
		deviceContext->IASetVertexBuffers(0, 2, vbs, stride, offset);
		deviceContext->IASetIndexBuffer(modelData->GetIndexBuffer(), DXGI_FORMAT_R32_UINT, 0);

		// Send the updated view-projection matrix and material to the InstancedShader.
		shader->SetViewProj(VP);
		shader->SetMaterial(*pMaterial);

		// "Refresh" the shader.
		pTechnique->GetPassByIndex(p)->Apply(0, deviceContext);

		// Draw the visible instances inside the passed InstanceMap.
		deviceContext->DrawIndexedInstanced(indexCount, instanceMap->GetNumVisible(), 0, 0, 0);
	}

	shader = nullptr;
}